/* * Dog - Addons * * Copyright (c) 2011-2014 Dario Bonino, Luigi De Russis * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package it.polito.elite.dog.addons.streamprocessor; import it.polito.elite.dog.addons.streamprocessor.queue.IncomingEventQueue; import it.polito.elite.dog.addons.streamprocessor.queue.OutgoingEventQueue; import it.polito.elite.dog.core.library.model.notification.CloseNotification; import it.polito.elite.dog.core.library.model.notification.DetectedNotification; import it.polito.elite.dog.core.library.model.notification.EventNotification; import it.polito.elite.dog.core.library.model.notification.IsPresentNotification; import it.polito.elite.dog.core.library.model.notification.MovementCeasedNotification; import it.polito.elite.dog.core.library.model.notification.MovementDetectedNotification; import it.polito.elite.dog.core.library.model.notification.NonParametricNotification; import it.polito.elite.dog.core.library.model.notification.NotDetectedNotification; import it.polito.elite.dog.core.library.model.notification.NotPresentNotification; import it.polito.elite.dog.core.library.model.notification.OffNotification; import it.polito.elite.dog.core.library.model.notification.OnNotification; import it.polito.elite.dog.core.library.model.notification.OpenNotification; import it.polito.elite.dog.core.library.model.notification.ParametricNotification; import it.polito.elite.dog.core.library.model.notification.PressedNotification; import it.polito.elite.dog.core.library.model.notification.ReleasedNotification; import it.polito.elite.dog.core.library.util.LogHelper; import it.polito.elite.stream.processing.EsperStreamProcessor; import it.polito.elite.stream.processing.StreamProcessor; import it.polito.elite.stream.processing.StreamProcessorSpecification; import it.polito.elite.stream.processing.addon.event.source.dog.xmlrpc.SensorDescriptor; import it.polito.elite.stream.processing.addon.event.source.dog.xmlrpc.xml.SensorCollectionType; import it.polito.elite.stream.processing.addon.event.source.dog.xmlrpc.xml.SensorData; import it.polito.elite.stream.processing.addon.event.source.dog.xmlrpc.xml.SourceToDeviceMappingSpecification; import it.polito.elite.stream.processing.addon.jobs.StartJob; import it.polito.elite.stream.processing.addon.management.ProcessorManager; import it.polito.elite.stream.processing.addon.management.config.UnitFormatManager; import it.polito.elite.stream.processing.addon.management.config.UpdateChainTask; import it.polito.elite.stream.processing.core.EventDrain; import it.polito.elite.stream.processing.events.GenericEvent; import it.polito.elite.stream.processing.events.RealEvent; import it.polito.elite.stream.processing.factory.EsperXMLStreamProcessorFactory; import it.polito.elite.stream.processing.interfaces.EventConsumer; import it.polito.elite.stream.processing.xml.StreamProcessingConfigurationType; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.Date; import java.util.Dictionary; import java.util.GregorianCalendar; import java.util.Hashtable; import java.util.Timer; import javax.measure.Measure; import javax.measure.quantity.Quantity; import org.osgi.framework.BundleContext; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.osgi.service.event.Event; import org.osgi.service.event.EventAdmin; import org.osgi.service.event.EventConstants; import org.osgi.service.event.EventHandler; import org.osgi.service.log.LogService; import org.quartz.CronExpression; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; /** * @author <a href="mailto:dario.bonino@polito.it">Dario Bonino</a> (original * version) * @author <a href="mailto:luigi.derussis@polito.it">Luigi De Russis</a> (minor * editing) * @see <a href="http://elite.polito.it">http://elite.polito.it</a> * */ public class SpChainsOSGi implements EventHandler, ManagedService, ProcessorManager, EventConsumer { // the static constants used for retrieving configuration parameters public static final String MAPPING_FILE = "processor.source.mapping"; public static final String PROCESSOR_FILE = "processor.XML"; public static final String PROCESSOR_REFRESH = "processor.XMLRefresh"; public static final String PROCESSOR_START_AT = "processor.startAt"; // the service logger private LogHelper logger; // the event admin service private EventAdmin eventAdmin; // the log id public static final String logId = "[SpChainsOSGi]: "; // the default refresh time public static final int defaultRefreshTimeMillis = 30000; // the processor file private File processorXMLFile; // the source mapping file private File sourceMappingFile; // the configuration refresh time in milliseconds private int refreshTimeMillis; // the stream processor instance private EsperStreamProcessor sp; // the source definitions private Hashtable<String, SensorDescriptor> sourceDefinitions; // the readiness flag private boolean ready; // the event handling queues private IncomingEventQueue inQueue; private OutgoingEventQueue outQueue; /** * The class constructor for common initializations... */ public SpChainsOSGi() { // set the ready flag at false this.ready = false; // init sources definition map this.sourceDefinitions = new Hashtable<String, SensorDescriptor>(); } /**************************************************************** * * Service Binding * ****************************************************************/ /** * Handle the bundle activation */ protected void activate(BundleContext ctx) { // init the logger with a null logger this.logger = new LogHelper(ctx); // log the activation this.logger .log(LogService.LOG_INFO, SpChainsOSGi.logId + "Activated SpChainsOSGi, processor is now configuring and starting."); // initialize the units of measure UnitFormatManager.SetUnitFormat(); } /** * Handle the bundle de-activation */ protected void deactivate() { // if running stop if ((this.sp != null) && (this.sp.isPlugged())) this.stop(); if (this.logger != null) this.logger.log(LogService.LOG_INFO, SpChainsOSGi.logId + "Deactivated SpChainsOSGi, processor is shutting down."); } protected void setEventAdmin(EventAdmin eventAdmin) { this.eventAdmin = eventAdmin; } protected void unsetEventAdmin(EventAdmin eventAdmin) { if (this.eventAdmin == eventAdmin) { this.eventAdmin = null; } } @Override public void updated(Dictionary<String, ?> properties) throws ConfigurationException { if (properties != null) { // get the configuration parameters String mappingFileName = (String) properties .get(SpChainsOSGi.MAPPING_FILE); String processorFileName = (String) properties .get(SpChainsOSGi.PROCESSOR_FILE); String refreshTimeMillisAsString = (String) properties .get(PROCESSOR_REFRESH); String startAt = (String) properties.get(PROCESSOR_START_AT); // debug this.logger.log(LogService.LOG_DEBUG, SpChainsOSGi.logId + "Mapping file: " + mappingFileName + " Processor file: " + processorFileName + " Refresh time (ms): " + refreshTimeMillisAsString); // check files this.sourceMappingFile = new File( System.getProperty("configFolder") + "/" + mappingFileName); this.processorXMLFile = new File(System.getProperty("configFolder") + "/" + processorFileName); if ((this.sourceMappingFile != null) && (this.processorXMLFile != null) && (this.sourceMappingFile.exists()) && (this.processorXMLFile.exists())) { // file exist, can configure the processor... if ((refreshTimeMillisAsString != null) && (!refreshTimeMillisAsString.isEmpty())) { // check the refresh time try { this.refreshTimeMillis = Integer .valueOf(refreshTimeMillisAsString); } catch (NumberFormatException e) { this.logger .log(LogService.LOG_WARNING, SpChainsOSGi.logId + "Refresh time cannot be parsed, setting the default refresh time at 30s"); } finally { this.refreshTimeMillis = SpChainsOSGi.defaultRefreshTimeMillis; } // everything ready, // initialize the stream processor this.initProcessor(this.processorXMLFile, this.refreshTimeMillis); // initialize the sources this.initSources(this.sourceMappingFile); // initialize the drains this.initDrains(); // initialize the queues this.inQueue = new IncomingEventQueue(this.logger, this.sp); this.outQueue = new OutgoingEventQueue(this.logger, this.eventAdmin); // handle scheduled start if (startAt != null) { try { // create a cron expression fro the given parameter CronExpression cronExp = new CronExpression(startAt); // get the next valid date Date startTime = cronExp .getNextValidTimeAfter(new Date()); // log the start time this.logger.log(LogService.LOG_INFO, SpChainsOSGi.logId + "Scheduled start at: " + startTime); // create a new scheduler SchedulerFactory sFactory = new StdSchedulerFactory(); Scheduler scheduler = sFactory.getScheduler(); // create a simple trigger SimpleTrigger trigger = new SimpleTrigger(); // set the trigger name trigger.setName("startTrigger"); // set the trigger to start at the next valid date trigger.setStartTime(startTime); // set fire count at 1 (just one call to start()) trigger.setRepeatCount(0); trigger.setRepeatInterval(10); // define a job to start (StartJob, basically calls // the // spManager.start()) method) JobDetail jobDetail = new JobDetail("start", "1", StartJob.class); jobDetail.getJobDataMap().put("managerToStart", this); scheduler.scheduleJob(jobDetail, trigger); // start the scheduler scheduler.start(); } catch (Exception e) { // log the error this.logger .log(LogService.LOG_WARNING, SpChainsOSGi.logId + "Unable to start at scheduled time, starting immediately..."); // direct start this.start(); } } else { // direct start this.start(); } this.ready = true; } } else { // error this.logger .log(LogService.LOG_ERROR, SpChainsOSGi.logId + "Configuration files are missing or empty, the stream processor service will not be running"); } } } @Override public void handleEvent(Event event) { if ((this.sp != null) && (this.sp.isPlugged())) { // filter own notifications if (!(event.containsProperty(EventConstants.BUNDLE_SYMBOLICNAME) && ((String) event .getProperty(EventConstants.BUNDLE_SYMBOLICNAME)) .equalsIgnoreCase(this.getClass().getSimpleName()))) { // debug this.logger.log(LogService.LOG_DEBUG, SpChainsOSGi.logId + "Received new measure " + event.getTopic()); // handle Notification Object eventContent = event.getProperty(EventConstants.EVENT); if ((this.sp != null) && (this.sp.isPlugged())) { if ((eventContent instanceof ParametricNotification)) { if (eventContent instanceof EventNotification) { // assume the event can be directly fed to the sp // processor... EventNotification receivedNotification = (EventNotification) eventContent; // extract the inner event if (receivedNotification.getEvent() instanceof GenericEvent) { GenericEvent innerEvent = (GenericEvent) receivedNotification .getEvent(); this.inQueue.addEvent(innerEvent); // debug this.logger.log( LogService.LOG_DEBUG, SpChainsOSGi.logId + "Received notification from " + innerEvent.getSrc() + " value " + innerEvent.getValue() + " " + innerEvent.getUnitOfMeasure() + " notification " + EventNotification.class .getSimpleName()); } } else { // store the received notification ParametricNotification receivedNotification = (ParametricNotification) eventContent; // the device uri String deviceURI = receivedNotification .getDeviceUri(); // the notification measure Measure<?, Quantity> value = this .getNotificationValue(receivedNotification); // get the notification name from the topic String topic = event.getTopic(); String notification = topic.substring(topic .lastIndexOf('/') + 1); // GetQFPARAM String qfParams = getNotificationQFParams(receivedNotification); // check the notification String iuuid = SensorDescriptor.generateInnerUUID( deviceURI, notification, qfParams); SensorDescriptor registeredSource = this.sourceDefinitions .get(iuuid); if (registeredSource != null) { // create the spChains event and post-it to the // incoming // queue RealEvent eventToPost = new RealEvent( registeredSource.getUid(), registeredSource.getUid(), GregorianCalendar.getInstance(), value.doubleValue(value.getUnit()), value.getUnit().toString()); // post the event this.inQueue.addEvent(eventToPost); // debug this.logger.log(LogService.LOG_DEBUG, SpChainsOSGi.logId + "Received notification from " + deviceURI + " value " + value + " notification " + notification); } } } else if (eventContent instanceof NonParametricNotification) { NonParametricNotification receivedNotification = (NonParametricNotification) eventContent; // the device uri String deviceURI = receivedNotification.getDeviceUri(); Double value = -1.0; // handle supported notifications (binary) if ((receivedNotification instanceof OnNotification) || (receivedNotification instanceof MovementDetectedNotification) || (receivedNotification instanceof OpenNotification) || (receivedNotification instanceof IsPresentNotification) || (receivedNotification instanceof DetectedNotification) || (receivedNotification instanceof PressedNotification)) { value = 1.0; } else if ((receivedNotification instanceof OffNotification) || (receivedNotification instanceof MovementCeasedNotification) || (receivedNotification instanceof CloseNotification) || (receivedNotification instanceof NotPresentNotification) || (receivedNotification instanceof NotDetectedNotification) || (receivedNotification instanceof ReleasedNotification)) { value = 0.0; } //value >=0 then the notification is supported if (value >= 0.0) { // get the notification name from the topic String topic = event.getTopic(); String notification = topic.substring(topic .lastIndexOf('/') + 1); // GetQFPARAM String qfParams = ""; // check the notification String iuuid = SensorDescriptor.generateInnerUUID( deviceURI, notification, qfParams); SensorDescriptor registeredSource = this.sourceDefinitions .get(iuuid); if (registeredSource != null) { // create the spChains event and post-it to the // incoming // queue RealEvent eventToPost = new RealEvent( registeredSource.getUid(), registeredSource.getUid(), GregorianCalendar.getInstance(), value,""); // post the event this.inQueue.addEvent(eventToPost); // debug this.logger.log(LogService.LOG_DEBUG, SpChainsOSGi.logId + "Received notification from " + deviceURI + " value " + value + " notification " + notification); } } } } } } } @SuppressWarnings("unchecked") private Measure<?, Quantity> getNotificationValue( ParametricNotification receivedNotification) { // the value, initially null Measure<?, Quantity> value = null; // get all the notification methods Method[] notificationMethods = receivedNotification.getClass() .getDeclaredMethods(); // extract the measure value... for (Method currentMethod : notificationMethods) { if (Measure.class.isAssignableFrom(currentMethod.getReturnType())) { try { // read the value value = (Measure<?, Quantity>) currentMethod.invoke( receivedNotification, new Object[] {}); break; } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return value; } private String getNotificationQFParams( ParametricNotification receivedNotification) { // get all the notification methods Field[] notificationFields = receivedNotification.getClass() .getDeclaredFields(); // prepare the buffer for parameters StringBuffer qfParams = new StringBuffer(); // the first flag boolean first = true; // extract the parameter values... for (Field currentField : notificationFields) { // check the current field to be different from deviceURI and from // measure if ((!currentField.getName().equals("deviceUri")) && (!currentField.getName().equals("notificationName")) && (!currentField.getName().equals("notificationTopic")) && (!(currentField.getType() .isAssignableFrom(Measure.class))) && (currentField.getType().isAssignableFrom(String.class))) { try { // append a quote if (first) first = false; else qfParams.append(","); // suppress access control currentField.setAccessible(true); // get the value qfParams.append(currentField.get(receivedNotification)); // reset access control currentField.setAccessible(false); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return qfParams.toString(); } /** * @return the eventAdmin */ public EventAdmin getEventAdmin() { return eventAdmin; } /** * * @param spConfigXMLFile * @param configRefreshMillis */ private void initProcessor(File spConfigXMLFile, int configRefreshMillis) { // get the processor specification StreamProcessingConfigurationType streamConfig = StreamProcessorSpecification .parseXMLSpecification(spConfigXMLFile); // get a stream processor factory EsperXMLStreamProcessorFactory factory = new EsperXMLStreamProcessorFactory(); // get the stream processor this.sp = (EsperStreamProcessor) factory .getStreamProcessor(streamConfig); // set-up a chain update watch dog Timer processorWatchDog = new Timer(); processorWatchDog.schedule( new UpdateChainTask(spConfigXMLFile, this.sp), configRefreshMillis, configRefreshMillis); } /** * * @param sourceMappingFile */ private void initSources(File sourceMappingFile) { // parse the mapping file SensorCollectionType mappingSpec = SourceToDeviceMappingSpecification .parseXMLSpecification(sourceMappingFile); // generate and store sensor descriptors for (SensorData sData : mappingSpec.getSensor()) { // create a sensor descriptor object SensorDescriptor desc = new SensorDescriptor(sData.getSensorURI(), sData.getSensorQFunctionality(), sData.getSensorQFParams(), sData.getUid()); // store the sensor descriptor this.sourceDefinitions.put(desc.getIUUID(), desc); } } private void initDrains() { if (this.sp != null) { // get the processor drains Collection<EventDrain> allDrains = this.sp.getAllDrains(); // iterate over the drains for (EventDrain drain : allDrains) { // separate consumer per drain to maximize concurrency drain.addConsumer(this); } } } @Override public void connect() { // Not used } @Override public void disconnect() { // Not used } @Override public boolean isConnected() { // Not used, default value return true; } @Override public StreamProcessor getSP() { return this.sp; } @Override public boolean isReady() { return this.ready; } @Override public void start() { // plug the processor this.sp.plug(); // start the queues this.inQueue.startQueue(); this.outQueue.startQueue(); } @Override public void stop() { // stop the queues this.inQueue.stopQueue(); this.outQueue.stopQueue(); // unplug this.sp.unPlug(); } @Override public void newEvent(GenericEvent event) { if ((event.getValue() != null))// && (event.getUnitOfMeasure() != null)) // enqueue the event this.outQueue.addEvent(event); } }